// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/media/session/audio_focus_manager.h"

#include "base/command_line.h"
#include "content/browser/media/session/media_session_impl.h"
#include "content/browser/media/session/media_session_player_observer.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread.h"
#include "content/test/test_web_contents.h"
#include "media/base/media_content_type.h"
#include "media/base/media_switches.h"

namespace content {

namespace {

    class MockMediaSessionPlayerObserver : public MediaSessionPlayerObserver {
    public:
        void OnSuspend(int player_id) override { }
        void OnResume(int player_id) override { }
        void OnSetVolumeMultiplier(
            int player_id, double volume_multiplier) override { }
        RenderFrameHost* GetRenderFrameHost() const override { return nullptr; }
    };

} // anonymous namespace

using AudioFocusType = AudioFocusManager::AudioFocusType;
using SuspendType = MediaSession::SuspendType;

class AudioFocusManagerTest : public testing::Test {
public:
    AudioFocusManagerTest()
        : ui_thread_(BrowserThread::UI, &message_loop_)
    {
    }

    void SetUp() override
    {
        base::CommandLine::ForCurrentProcess()->AppendSwitch(
            switches::kEnableDefaultMediaSession);
        rph_factory_.reset(new MockRenderProcessHostFactory());
        SiteInstanceImpl::set_render_process_host_factory(rph_factory_.get());
        browser_context_.reset(new TestBrowserContext());
        pepper_observer_.reset(new MockMediaSessionPlayerObserver());
    }

    void TearDown() override
    {
        browser_context_.reset();
        SiteInstanceImpl::set_render_process_host_factory(nullptr);
        rph_factory_.reset();
    }

    MediaSessionImpl* GetAudioFocusedSession() const
    {
        const auto& audio_focus_stack = AudioFocusManager::GetInstance()->audio_focus_stack_;
        for (auto iter = audio_focus_stack.rbegin();
             iter != audio_focus_stack.rend(); ++iter) {
            if ((*iter)->audio_focus_type() == AudioFocusManager::AudioFocusType::Gain)
                return (*iter);
        }
        return nullptr;
    }

    int GetTransientMaybeDuckCount() const
    {
        int count = 0;
        const auto& audio_focus_stack = AudioFocusManager::GetInstance()->audio_focus_stack_;
        for (auto iter = audio_focus_stack.rbegin();
             iter != audio_focus_stack.rend(); ++iter) {
            if ((*iter)->audio_focus_type() == AudioFocusManager::AudioFocusType::GainTransientMayDuck) {
                ++count;
            } else {
                break;
            }
        }

        return count;
    }

    double IsSessionDucking(MediaSessionImpl* session)
    {
        return session->is_ducking_; // Quack! Quack!
    }

    void RequestAudioFocus(MediaSessionImpl* session,
        AudioFocusManager::AudioFocusType audio_focus_type)
    {
        session->RequestSystemAudioFocus(audio_focus_type);
    }

    void AbandonAudioFocus(MediaSessionImpl* session)
    {
        session->AbandonSystemAudioFocusIfNeeded();
    }

    WebContents* CreateWebContents()
    {
        return TestWebContents::Create(browser_context_.get(),
            SiteInstance::SiteInstance::Create(browser_context_.get()));
    }

    std::unique_ptr<MediaSessionPlayerObserver> pepper_observer_;

private:
    base::MessageLoopForUI message_loop_;
    TestBrowserThread ui_thread_;

    std::unique_ptr<MockRenderProcessHostFactory> rph_factory_;
    std::unique_ptr<TestBrowserContext> browser_context_;
};

TEST_F(AudioFocusManagerTest, InstanceAvailableAndSame)
{
    AudioFocusManager* audio_focus_manager = AudioFocusManager::GetInstance();
    ASSERT_TRUE(!!audio_focus_manager);
    ASSERT_EQ(audio_focus_manager, AudioFocusManager::GetInstance());
}

TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_ReplaceFocusedEntry)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
    MediaSessionImpl* media_session_3 = MediaSessionImpl::Get(web_contents_3.get());

    ASSERT_EQ(nullptr, GetAudioFocusedSession());

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session_1, GetAudioFocusedSession());

    RequestAudioFocus(media_session_2, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session_2, GetAudioFocusedSession());

    RequestAudioFocus(media_session_3, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session_3, GetAudioFocusedSession());
}

TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_Duplicate)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    ASSERT_EQ(nullptr, GetAudioFocusedSession());

    RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session, GetAudioFocusedSession());

    RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session, GetAudioFocusedSession());
}

TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_FromTransient)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    RequestAudioFocus(
        media_session, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_EQ(nullptr, GetAudioFocusedSession());
    ASSERT_EQ(1, GetTransientMaybeDuckCount());

    RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session, GetAudioFocusedSession());
    ASSERT_EQ(0, GetTransientMaybeDuckCount());
}

TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGain)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session, GetAudioFocusedSession());
    ASSERT_EQ(0, GetTransientMaybeDuckCount());

    RequestAudioFocus(
        media_session, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_EQ(nullptr, GetAudioFocusedSession());
    ASSERT_EQ(1, GetTransientMaybeDuckCount());
    ASSERT_FALSE(IsSessionDucking(media_session));
}

TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(0, GetTransientMaybeDuckCount());
    ASSERT_FALSE(IsSessionDucking(media_session_1));

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_EQ(1, GetTransientMaybeDuckCount());
    ASSERT_TRUE(IsSessionDucking(media_session_1));

    RequestAudioFocus(
        media_session_1, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_EQ(2, GetTransientMaybeDuckCount());
    ASSERT_FALSE(IsSessionDucking(media_session_1));
}

TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesFocusedEntry)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session, GetAudioFocusedSession());

    AbandonAudioFocus(media_session);
    ASSERT_EQ(nullptr, GetAudioFocusedSession());
}

TEST_F(AudioFocusManagerTest, AbandonAudioFocus_NoAssociatedEntry)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    AbandonAudioFocus(media_session);
    ASSERT_EQ(nullptr, GetAudioFocusedSession());
}

TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesTransientEntry)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    RequestAudioFocus(
        media_session, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_EQ(1, GetTransientMaybeDuckCount());

    AbandonAudioFocus(media_session);
    ASSERT_EQ(0, GetTransientMaybeDuckCount());
}

TEST_F(AudioFocusManagerTest, AbandonAudioFocus_WhileDuckingThenResume)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(0, GetTransientMaybeDuckCount());
    ASSERT_FALSE(IsSessionDucking(media_session_1));

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_EQ(1, GetTransientMaybeDuckCount());
    ASSERT_TRUE(IsSessionDucking(media_session_1));

    AbandonAudioFocus(media_session_1);
    ASSERT_EQ(1, GetTransientMaybeDuckCount());

    AbandonAudioFocus(media_session_2);
    ASSERT_EQ(0, GetTransientMaybeDuckCount());

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_FALSE(IsSessionDucking(media_session_1));
}

TEST_F(AudioFocusManagerTest, AbandonAudioFocus_StopsDucking)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(0, GetTransientMaybeDuckCount());
    ASSERT_FALSE(IsSessionDucking(media_session_1));

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_EQ(1, GetTransientMaybeDuckCount());
    ASSERT_TRUE(IsSessionDucking(media_session_1));

    AbandonAudioFocus(media_session_2);
    ASSERT_EQ(0, GetTransientMaybeDuckCount());
    ASSERT_FALSE(IsSessionDucking(media_session_1));
}

TEST_F(AudioFocusManagerTest, DuckWhilePlaying)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_FALSE(IsSessionDucking(media_session_1));

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_TRUE(IsSessionDucking(media_session_1));
}

TEST_F(AudioFocusManagerTest, GainSuspendsTransient)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_TRUE(media_session_2->IsSuspended());
}

TEST_F(AudioFocusManagerTest, DuckWithMultipleTransients)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
    MediaSessionImpl* media_session_3 = MediaSessionImpl::Get(web_contents_3.get());

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_FALSE(IsSessionDucking(media_session_1));

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_TRUE(IsSessionDucking(media_session_1));

    RequestAudioFocus(
        media_session_3, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_TRUE(IsSessionDucking(media_session_1));

    AbandonAudioFocus(media_session_2);
    ASSERT_TRUE(IsSessionDucking(media_session_1));

    AbandonAudioFocus(media_session_3);
    ASSERT_FALSE(IsSessionDucking(media_session_1));
}

TEST_F(AudioFocusManagerTest, WebContentsDestroyed_ReleasesFocus)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_EQ(media_session, GetAudioFocusedSession());

    web_contents.reset();
    ASSERT_EQ(nullptr, GetAudioFocusedSession());
}

TEST_F(AudioFocusManagerTest, WebContentsDestroyed_ReleasesTransients)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    RequestAudioFocus(
        media_session, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_EQ(1, GetTransientMaybeDuckCount());

    web_contents.reset();
    ASSERT_EQ(0, GetTransientMaybeDuckCount());
}

TEST_F(AudioFocusManagerTest, WebContentsDestroyed_StopsDucking)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
    ASSERT_FALSE(IsSessionDucking(media_session_1));

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
    ASSERT_TRUE(IsSessionDucking(media_session_1));

    web_contents_2.reset();
    ASSERT_FALSE(IsSessionDucking(media_session_1));
}

TEST_F(AudioFocusManagerTest, PepperRequestsGainFocus)
{
    std::unique_ptr<WebContents> web_contents(CreateWebContents());
    MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

    media_session->AddPlayer(
        pepper_observer_.get(), 0, media::MediaContentType::Pepper);
    ASSERT_EQ(media_session, GetAudioFocusedSession());

    media_session->RemovePlayer(pepper_observer_.get(), 0);
    ASSERT_EQ(nullptr, GetAudioFocusedSession());
}

TEST_F(AudioFocusManagerTest, GainDucksPepper)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    media_session_1->AddPlayer(
        pepper_observer_.get(), 0, media::MediaContentType::Pepper);

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::Gain);

    ASSERT_EQ(media_session_2, GetAudioFocusedSession());
    ASSERT_TRUE(media_session_1->IsActive());
    ASSERT_TRUE(IsSessionDucking(media_session_1));
}

TEST_F(AudioFocusManagerTest, AbandoningGainFocusRevokesTopMostPepperSession)
{
    std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
    MediaSessionImpl* media_session_1 = MediaSessionImpl::Get(web_contents_1.get());

    std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
    MediaSessionImpl* media_session_2 = MediaSessionImpl::Get(web_contents_2.get());

    std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
    MediaSessionImpl* media_session_3 = MediaSessionImpl::Get(web_contents_3.get());

    media_session_1->AddPlayer(
        pepper_observer_.get(), 0, media::MediaContentType::Pepper);

    RequestAudioFocus(
        media_session_2, AudioFocusManager::AudioFocusType::Gain);
    RequestAudioFocus(
        media_session_3, AudioFocusManager::AudioFocusType::Gain);

    ASSERT_EQ(media_session_3, GetAudioFocusedSession());
    ASSERT_TRUE(media_session_2->IsReallySuspended());
    ASSERT_TRUE(media_session_1->IsActive());
    ASSERT_TRUE(IsSessionDucking(media_session_1));

    AbandonAudioFocus(media_session_3);
    ASSERT_EQ(media_session_1, GetAudioFocusedSession());
}

} // namespace content
